#include "Listener.h"

#include "OgreOverlayManager.h"
#include "OgreRoot.h"
#include "OgreStringConverter.h"
#include "OgreTextAreaOverlayElement.h"

#include "OgreLogManager.h"
#include "OgreMaterialManager.h"
#include "OgreMaterial.h"

#define ROTATION_SPEED 10
#define MOVEMENT_SPEED 500

void TW_CALL DeviceListener::RunCB(void* clientData)
{
    static_cast<DeviceListener*>(clientData)->shutDown();
}

void TW_CALL DeviceListener::setSkyboxCallback(const void *value, void *clientData)
{
    if(*static_cast<const bool *>(value) && !static_cast<DeviceListener*>(clientData)->mSceneMgr->isSkyBoxEnabled())
    {
        //static_cast<DeviceListener*>(clientData)->getSceneMgr()->setSkyBox(true, "Sky", 5, 8, 4000);
        static_cast<DeviceListener*>(clientData)->getSceneMgr()->setSkyBox(true, "Sky", 4000);
    }
    else
    {
        //static_cast<DeviceListener*>(clientData)->getSceneMgr()->setSkyBox(false, "Sky", 5, 8, 4000);
        static_cast<DeviceListener*>(clientData)->getSceneMgr()->setSkyBox(false, "Sky", 4000);
    }
}

void TW_CALL DeviceListener::getSkyboxCallback(void *value, void *clientData)
{
    *static_cast<bool *>(value) = static_cast<DeviceListener *>(clientData)->getSceneMgr()->isSkyBoxEnabled();
}

DeviceListener::DeviceListener(Ogre::RenderWindow* win, Ogre::Camera* cam, Ogre::SceneManager *sceneMgr)
{
#if defined OIS_LINUX_PLATFORM
    mDpy = XOpenDisplay(0);
#endif
    mMouseLock = false;

    mExit = false;
    // Populate the camera and scene manager containers
    mSceneMgr = sceneMgr;
	camera = cam;
	mWindow = win;

	// Get the camera node
	mPitchNode = camera->getParentSceneNode();
    mCamNode = mPitchNode->getParentSceneNode();
	mCubeMat = static_cast<Ogre::MaterialPtr>(Ogre::MaterialManager::getSingleton().getByName("CubeMaterial"));
	mCubeNode = mSceneMgr->getSceneNode("CubeNode");

    // continue rendering
    mContinue = true;

	// Initialize the direction vector
    mDirection = Ogre::Vector3::ZERO;
    yaw = 0;
    pitch = 0;

	// Initialize OIS
	OIS::ParamList params;
	size_t windowHnd = 0;
	std::ostringstream windowHndStr;

	mWindow->getCustomAttribute("WINDOW", &windowHnd);
	windowHndStr << windowHnd;

    params.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
#if defined OIS_WIN32_PLATFORM
    params.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_FOREGROUND" )));
    params.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_NONEXCLUSIVE")));
    params.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_FOREGROUND")));
    params.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_NONEXCLUSIVE")));
#elif defined OIS_LINUX_PLATFORM
    params.insert(std::make_pair(std::string("x11_mouse_grab"), std::string("false")));
    params.insert(std::make_pair(std::string("x11_keyboard_grab"), std::string("false")));
#endif

	mInputManager = OIS::InputManager::createInputSystem(params);

	mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, true));
	mMouse->setEventCallback(this);

    mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, true));
	mKeyboard->setEventCallback(this);

	windowResized(mWindow);
	Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);

	//Create the Window Manager
	mWinManager = new TwOgre::WindowManager(mWindow, mSceneMgr);

	/*
	** Create the Window that will hold control that affect the environment
	** then set its position and size.
	*/
    mWin1 = mWinManager->createWindow(TwOgre::WindowParams("Window 1")
        .title("World Editor")
        .color(Ogre::ColourValue(1.0, 0.0, 0.0, 0.7))
        .position(mWindow->getWidth()-408, 0)
        .size(400, 380)
        .valuesWidth(240)
        );

    /*
	** Now we'll add two ColorVariables, one that changes the color of the cube and
	** one that changed the color of the sky, and a BoolVariable that will eventually
	** turn the SkyBox on and off when I get callbacks working. ;)
	*/
    mBoxColor = mWin1->addColorVariable(TwOgre::ColorParams("Color1")
        .label("Cube Color")
        .value(Ogre::ColourValue(1.0, 0.0, 0.0, 0.7))
        .group("Color Values"));

    mBgColor = mWin1->addColorVariable(TwOgre::ColorParams("Color2")
        .label("Sky Color")
        .value(Ogre::ColourValue::Black)
        .group("Color Values"));

    mSkyBox = mWin1->addBoolVariable(TwOgre::BoolParams("Bool2")
        .readOnly(false)
        .label("Show Skybox")
        .value(false)
        .shortcut("Alt+b")
        .callback(&DeviceListener::setSkyboxCallback, &DeviceListener::getSkyboxCallback, this)
        );

	/*
	** Now we add an IntegerVariable and a RealVariable.  The IntegerVariable will
	** change the height of the cube and is limited from 0 - 100.  The RealVariable
	** will change how fast the cube is spinning (in degrees per second) and is
	** limited from 0 - 720.  The third argument to the limits is the step, this is
	** how much the value changes per "tick." By default the step is 1.  Finally we
	** add another BoolVariable that tells the cube whether or not to spin. Notice
	** that we are able to change what the value displays for true and false.
	*/
    mBoxHeight = mWin1->addIntegerVariable(TwOgre::IntegerParams("Integer1")
        .label("Cube Height")
        .value(50)
        .limits(0, 100)
        .shortcut("Ctrl+z", "Ctrl+Z"));

    mBoxRotSpeed = mWin1->addRealVariable(TwOgre::RealParams("Real1")
        .label("Cube Rotation")
    	.limits(0.0, 720.0, 10.0)
        .value(90));

    mRotateCube = mWin1->addBoolVariable(TwOgre::BoolParams("Bool1")
        .label("Rotate Cube")
        .value(true)
        .displayStrings("Spin", "No Spin")
        .shortcut("R"));

    theSep = mWin1->addSeparator("sep1");

    mFrameRate = mWin1->addRealVariable(TwOgre::RealParams("FrameRate")
        .readOnly(true)
        .label("FPS"));

    mCameraDirection = mWin1->addDirection3DVariable(TwOgre::Direction3DParams("CameraDirection")
        .readOnly(true)
        .label("Camera Direction"));

    mCameraOrientation = mWin1->addQuaternionVariable(TwOgre::QuaternionParams("CameraOrientation")
        .label("Camera Orientation")
        //.arrow(true)
        );

    mCameraPosition = mWin1->addPosition3DVariable(TwOgre::Position3DParams("campos")
        .label("Camera Position"));

    TwOgre::SeparatorVariable* sep2 = mWin1->addSeparator("sep2");

    theButton = mWin1->addButton(TwOgre::ButtonParams("theButton")
        .label("Exit")
        .help("Exit Application")
        .callback(&DeviceListener::RunCB, this)
        .shortcut("Alt+x"));

    TwOgre::SeparatorVariable* sep23 = mWin1->addSeparator("sep3");

    /*
	** Now we're going to add shortcut keys to some of the variables.  Shortcuts
	** are REALLY flexible, they can contain, Alt, Ctrl, numbers, "special" keys,
	** upper and lower case and any combination of these!
	*/
	mBoxRotSpeed->setShortcut("Ctrl+Alt+z", "Ctrl+Alt+x");

	/*
	** Create a couple of dummy window to show more colors
	*/
    mWin2 = mWinManager->createWindow(TwOgre::WindowParams("Window 2")
        .title("Another Window with Enums!")
        .area(0, 0, 350, 225)
        .valuesWidth(225)
        );

    mWindowColor = mWin2->addColorVariable(TwOgre::ColorParams("Color")
        .value(Ogre::ColourValue(1.0, 0.757, 0.145)));

    mWindowPosX = mWin2->addIntegerVariable(TwOgre::IntegerParams("X Pos")
        .limits(0, mWindow->getWidth()));

    mWindowPosY = mWin2->addIntegerVariable(TwOgre::IntegerParams("Y Pos")
        .limits(0, mWindow->getHeight()));

	TwOgre::EnumValueList enumList;
	enumList.push_back(TwOgre::EnumValue(1,"One"));
	enumList.push_back(TwOgre::EnumValue(2,"Two"));
	enumList.push_back(TwOgre::EnumValue(3,"Three"));
	enumList.push_back(TwOgre::EnumValue(4,"Four"));

    TwOgre::EnumVariable *enumVar = mWin2->addEnumVariable(TwOgre::EnumParams("Enum")
        .label("EnumVar")
        .enumValues(enumList)
        .value(3)
        .shortcut("o", "p")
        );

	TwOgre::EnumValueList enumList1;
	enumList1.push_back(TwOgre::EnumValue(9,"Nine"));
	enumList1.push_back(TwOgre::EnumValue(8,"Eight"));
	enumList1.push_back(TwOgre::EnumValue(7,"Seven"));
	enumList1.push_back(TwOgre::EnumValue(6,"Six"));
	enumList1.push_back(TwOgre::EnumValue(5,"Five"));
	enumVar->addEnumValues(enumList1);

    mWindowName = mWin2->addStringVariable(TwOgre::StringParams("WindowName")
        .label("Window Name")
        .value("Another Window with Enums!"));
}

DeviceListener::~DeviceListener()
{
	delete mWinManager;
	Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this);
	windowClosed(mWindow);
}

void DeviceListener::windowResized(Ogre::RenderWindow* rw)
{
	unsigned int width, height, depth;
	int left, top;
	rw->getMetrics(width, height, depth, left, top);

	const OIS::MouseState &ms = mMouse->getMouseState();
	ms.width = width;
	ms.height = height;
}

void DeviceListener::windowClosed(Ogre::RenderWindow* rw)
{
	if(rw == mWindow)
	{
		if(mInputManager)
		{
			mInputManager->destroyInputObject(mMouse);
			mInputManager->destroyInputObject(mKeyboard);

			OIS::InputManager::destroyInputSystem(mInputManager);
			mInputManager = 0;
		}

		Ogre::Root::getSingleton().removeFrameListener(this);
		Ogre::Root::getSingleton().queueEndRendering();
	}
}

// Most code will go here. This gets called every frame
bool DeviceListener::frameStarted(const Ogre::FrameEvent& evt)
{
    if(mExit) return false;

    // Update values from Window 1
	camera->getViewport()->setBackgroundColour(mBgColor->getColorValue());
	mCubeMat->setAmbient(mBoxColor->getColorValue());
	mCubeMat->setDiffuse(mBoxColor->getColorValue());
	Ogre::Vector3 position = mCubeNode->getPosition();
	position.y = mBoxHeight->getIntegerValue();
	mCubeNode->setPosition(position);
	if(mRotateCube->getBoolValue())
	{
		mCubeNode->yaw(Ogre::Degree(mBoxRotSpeed->getRealValue() * evt.timeSinceLastFrame));
	}

	// Update values from Window 2
	mWin2->setColor(mWindowColor->getColorValue());
	mWin2->setPosition(Ogre::Vector2(mWindowPosX->getIntegerValue(), mWindowPosY->getIntegerValue()));
	mWin2->setTitle(mWindowName->getStringValue());

    mCameraPosition->setVector3Value(mCamNode->_getDerivedPosition());

    //if(mSkyBox->getBoolValue() && !mSceneMgr->isSkyBoxEnabled())
	//{
	//	mSceneMgr->setSkyBox(true, "Sky", 5, 8, 4000);
	//}
	//else if(!mSkyBox->getBoolValue() && mSceneMgr->isSkyBoxEnabled())
	//{
	//	mSceneMgr->setSkyBox(false, "Sky", 5, 8, 4000);
	//}

	mMouse->capture();
#if defined OIS_LINUX_PLATFORM
/*    if( (mMouse->getMouseState().X.abs >= 0) && (mMouse->getMouseState().X.abs < mWindow->getWidth()) &&
        (mMouse->getMouseState().Y.abs >= 0) && (mMouse->getMouseState().Y.abs < mWindow->getHeight())    )
    {
        int x_center = (mWindow->getWidth() / 2);
        int y_center = (mWindow->getHeight() / 2);
        int x_dist = x_center - mMouse->getMouseState().X.abs;
        int y_dist = y_center - mMouse->getMouseState().Y.abs;

        if( x_dist != 0 || y_dist != 0 )
        {
            mMouseLock = true;
            XWarpPointer(mDpy, None, None, 0, 0, 0, 0, x_dist, y_dist);
            XFlush(mDpy);
        }
        else
        {
            mMouseLock = false;
        }
    }*/
#endif

    mKeyboard->capture();

	// Rotate the camera
    mCamNode->yaw(yaw * evt.timeSinceLastFrame);
    mPitchNode->pitch(pitch * evt.timeSinceLastFrame);
    // Reset the variables
    yaw = pitch = 0;

    // Move the camera
    mCamNode->translate(mPitchNode->_getDerivedOrientation() * mDirection * evt.timeSinceLastFrame);

	mFrameRate->setRealValue(mWindow->getAverageFPS());

    //mCameraOrientation->setQuaternionValue(mCamNode->_getDerivedOrientation());
    mCamNode->setOrientation(mCameraOrientation->getQuaternionValue());
    mCameraDirection->setDirection3DValue(mCamNode->_getDerivedOrientation() * -Ogre::Vector3::UNIT_Z);

	return  mContinue;
}

// Mouse event callbacks
bool DeviceListener::mouseMoved(const OIS::MouseEvent &arg)
{
    if( !mMouseLock )
    {
        mWinManager->injectMouseMoved(arg);

        if(arg.state.buttonDown(OIS::MB_Right))
        {
            // This populates the camera rotation variables
            yaw = -Ogre::Degree(arg.state.X.rel * ROTATION_SPEED);
            pitch = -Ogre::Degree(arg.state.Y.rel * ROTATION_SPEED);
        }
    }
	return true;
}

bool DeviceListener::mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
	mWinManager->injectMousePressed(arg, id);
	return true;
}

bool DeviceListener::mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
	mWinManager->injectMouseReleased(arg, id);
	return true;
}

// KeyListener Callbacks
bool DeviceListener::keyPressed(const OIS::KeyEvent &arg)
{
	mWinManager->injectKeyPressed(arg);

	static int count = 0;
	switch(arg.key)
    {
		// AntTweakBar is handling application exit now
        //case OIS::KC_ESCAPE:
  //          mContinue = false;
  //          break;

		case OIS::KC_SYSRQ:
			mWindow->writeContentsToFile("ScreenShot"+Ogre::StringConverter::toString(count++)+".png");
			break;

		case OIS::KC_UP:
		case OIS::KC_W:
            mDirection.z -= MOVEMENT_SPEED;
            break;

        case OIS::KC_DOWN:
		case OIS::KC_S:
            mDirection.z += MOVEMENT_SPEED;
            break;

        case OIS::KC_LEFT:
		case OIS::KC_A:
            mDirection.x -= MOVEMENT_SPEED;
            break;

        case OIS::KC_RIGHT:
		case OIS::KC_D:
            mDirection.x += MOVEMENT_SPEED;
            break;

		case OIS::KC_PGUP:
		case OIS::KC_E:
			mDirection.y += MOVEMENT_SPEED;
			break;

		case OIS::KC_PGDOWN:
		case OIS::KC_Q:
			mDirection.y -= MOVEMENT_SPEED;
			break;
    }
	return true;
}

bool DeviceListener::keyReleased(const OIS::KeyEvent &arg)
{
	mWinManager->injectKeyReleased(arg);

	switch(arg.key)
    {
		case OIS::KC_UP:
		case OIS::KC_W:
            mDirection.z += MOVEMENT_SPEED;
            break;

        case OIS::KC_DOWN:
		case OIS::KC_S:
            mDirection.z -= MOVEMENT_SPEED;
            break;

        case OIS::KC_LEFT:
		case OIS::KC_A:
            mDirection.x += MOVEMENT_SPEED;
            break;

        case OIS::KC_RIGHT:
		case OIS::KC_D:
            mDirection.x -= MOVEMENT_SPEED;
            break;

		case OIS::KC_PGUP:
		case OIS::KC_E:
			mDirection.y -= MOVEMENT_SPEED;
			break;

		case OIS::KC_PGDOWN:
		case OIS::KC_Q:
			mDirection.y += MOVEMENT_SPEED;
			break;
    }
	return true;
}
